#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char *argv[]) {
  struct sockaddr_in6 serv, client_addr;
  struct addrinfo *iaddr = NULL, imatch;
  int sock, tam, flag=1, nerro=0, pid;
  char *tmp, c, destino[100], interface[10], buffer[100], *paddr;
  FILE *log;
  time_t agora;

  unlink("mobilidade.log");
  interface[0] = destino[0] = '\0';
  while ((c=getopt(argc, argv, "d:i:")) != -1) {
    switch (c) {
      case 'd':
        strcpy(destino, optarg);
        break;
      case 'i':
        strcpy(interface, optarg);
        break;
    }
  }

  if (destino[0] == '\0') {
    printf("Especificar destino...\n");
    exit(0);
  }

  pid = fork();
  if (pid < 0) {
    printf("Erro ao criar processo paralelo. Finalizando...\n");
    return -1;
  }
  if (pid == 0) {
    sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) {
      printf("[TCP] Problemas ao criar socket!\n");
      agora = time(NULL);
      log = fopen("mobilidade.log", "a+");
      fprintf(log, "[%s] [TCP] Problemas ao criar socket!\n", asctime(localtime(&agora)));
      fclose(log);
    }
    if (interface[0] != '\0')
      setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface, sizeof(interface)+1);
  
    memset(&serv, 0, sizeof(struct sockaddr_in6));
    serv.sin6_family   = AF_INET6;
    serv.sin6_port     = htons(10001);
    serv.sin6_flowinfo = 0;
    serv.sin6_scope_id = 0;
    tmp = (char *)(&serv.sin6_addr);
    inet_pton(AF_INET6, destino, tmp);
    if (connect(sock, (struct sockaddr *)&serv, sizeof(serv)) < 0) {
      printf("[TCP] Erro no connect (%i): %s\n", errno, strerror(errno));
      agora = time(NULL);
      log = fopen("mobilidade.log", "a+");
      fprintf(log, "[%s] [TCP] Erro no connect (%i): %s\n", asctime(localtime(&agora)), errno, strerror(errno));
      fclose(log);
    }

    while (1) {
      if (nerro > 10) {
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [TCP] Excedeu 10 erros. Teste de TCP finalizado\n", asctime(localtime(&agora)));
        fclose(log);
        printf("[TCP] Excedeu 10 erros. Teste de TCP finalizado\n");
        return -2;
      }
      sprintf(buffer, "Flag = %i\0", flag++);
      if (send(sock, buffer, strlen(buffer), 0) < 0) {
        printf("[TCP] Send falhado (%i): %s\n", errno, strerror(errno));
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [TCP] Send falhado (%i): %s\n", asctime(localtime(&agora)), errno, strerror(errno));
        fclose(log);
        nerro++;
      }
      else {
        printf("[TCP] Enviado: %s\n", buffer);
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [TCP] Enviado: %s\n", asctime(localtime(&agora)), buffer);
        fclose(log);
      }
      sleep(1);
    }
  } else {
    sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
      printf("[UDP] Problemas ao criar socket!\n");
      agora = time(NULL);
      log = fopen("mobilidade.log", "a+");
      fprintf(log, "[%s] [UDP] Problemas ao criar socket!\n", asctime(localtime(&agora)));
      fclose(log);
    }
    if (interface[0] != '\0')
      setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface, sizeof(interface)+1);
    serv.sin6_family   = AF_INET6;
    serv.sin6_port     = htons(10002);
    serv.sin6_flowinfo = 0;
    serv.sin6_scope_id = 0;

    bzero(&imatch, sizeof(imatch));
    imatch.ai_family = AF_INET6;
    if (getaddrinfo(destino, NULL, &imatch, &iaddr) == 0) {
      paddr = (char *) malloc(iaddr->ai_addrlen);
      memcpy(paddr, iaddr->ai_addr, iaddr->ai_addrlen);
      freeaddrinfo(iaddr);
      memcpy(&(serv.sin6_addr), &((struct sockaddr_in6 *)paddr)->sin6_addr, (size_t) sizeof(struct in6_addr));
    } else {
      printf("[UDP] Problema ao resgatar informacoes do servidor.\n");
      agora = time(NULL);
      log = fopen("mobilidade.log", "a+");
      fprintf(log, "[%s] [UDP] Problema ao resgatar informacoes do servidor.\n", asctime(localtime(&agora)));
      fclose(log);
      return -1;
    }
    while (1) {
      if (nerro > 10) {
        printf("[UDP] Excedeu 10 erros. Teste de UDP finalizado\n");
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [UDP] Excedeu 10 erros. Teste de UDP finalizado\n", asctime(localtime(&agora)));
        fclose(log);
        wait();
        return 0;
      }
      sprintf(buffer, "Flag = %i\0", flag++);
      if (sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr *)&serv, sizeof(serv)) < 0) {
        printf("[UDP] Send falhado (%i): %s\n", errno, strerror(errno));
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [UDP] Send falhado (%i): %s\n", asctime(localtime(&agora)), errno, strerror(errno));
        fclose(log);
        nerro++;
      }
      else {
        printf("[UDP] Enviado: %s\n", buffer);
        agora = time(NULL);
        log = fopen("mobilidade.log", "a+");
        fprintf(log, "[%s] [UDP] Enviado: %s\n", asctime(localtime(&agora)), buffer);
        fclose(log);
      }
      sleep(1);
    }
  }
}
